package com.baidu.fsg.uid.impl;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.baidu.fsg.uid.IdMappingService;
import com.baidu.fsg.uid.worker.dao.IdMappingRepository;
import com.baidu.fsg.uid.worker.entity.IdMapping;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.tinem.platform.module.pojo.co.Commons;
import com.tinem.platform.module.pojo.co.RedisKeyEnum;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author fengzhihao
 * @version v1
 * @program: platform
 * @className IdMappingServiceImpl
 * @description TODO
 * @site
 * @company
 * @create 2021/12/3 18:47:26
 */
@DS("uid")
public class IdMappingServiceImpl implements IdMappingService , ApplicationListener<ApplicationReadyEvent> {

    private static IdMappingService ID_MAPPING_SERVICE;

    public static IdMappingService getIdMappingService(){
        return ID_MAPPING_SERVICE;
    }

    @Resource
    StringRedisTemplate stringRedisTemplate;

    Cache<String, String> id_uuid = CacheUtil.newLRUCache(100000);
    Cache<String, String> uuid_id = CacheUtil.newLRUCache(100000);

    @Resource
    IdMappingRepository idMappingRepository;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void addId(String id) {
        IdMapping idMapping = new IdMapping();
        idMapping.setId(id);
        idMapping.setUuid(UUID.randomUUID().toString());
        //优化id映射，将id保存到redis中。
//        idMappingRepository.save(idMapping);
        id_uuid.put(idMapping.getId(),idMapping.getUuid(), Duration.ofDays(3).getSeconds()*1000);
        uuid_id.put(idMapping.getUuid(),idMapping.getId(), Duration.ofDays(3).getSeconds()*1000);
        stringRedisTemplate.opsForValue().set(RedisKeyEnum.id_uuid.getKey(idMapping.getId()), idMapping.getUuid(), Duration.ofDays(3));
        stringRedisTemplate.opsForValue().set(RedisKeyEnum.uuid_id.getKey(idMapping.getUuid()), idMapping.getId(), Duration.ofDays(3));
        stringRedisTemplate.opsForList().rightPush(RedisKeyEnum.id_mapping_query.getKey(),idMapping.getId());

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class,readOnly = true)
    public String getUUID(String id) {
        String uuid = id_uuid.get(id);
        if(StrUtil.isNotEmpty(uuid) && !Commons.NULL.equals(uuid)){
            return uuid;
        }
        String idUuidKey = RedisKeyEnum.id_uuid.getKey(id);
        uuid = stringRedisTemplate.opsForValue().get(idUuidKey);
        if(StrUtil.isEmpty(uuid)){
            synchronized (idUuidKey){
                uuid = stringRedisTemplate.opsForValue().get(idUuidKey);
                if(StrUtil.isEmpty(uuid)){
                    uuid = idMappingRepository.findUUIdById(id);
                    if(StrUtil.isEmpty(uuid)){
                        uuid = Commons.NULL;
                    }
                }
            }
        }
        if(Commons.NULL.equals(uuid)){
            id_uuid.put(id,uuid, Duration.ofDays(1).getSeconds()*1000);
            stringRedisTemplate.opsForValue().set(idUuidKey, uuid, Duration.ofSeconds(1));
            return null;
        }else{
            id_uuid.put(id,uuid, Duration.ofDays(3).getSeconds()*1000);
            stringRedisTemplate.opsForValue().set(idUuidKey, uuid, Duration.ofDays(3));
        }
        return uuid;
    }

    @Override
    public List<String> getUUID(List<String> ids) {
        return ids.stream().map(this::getUUID).filter(StrUtil::isNotEmpty).collect(Collectors.toList());
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class,readOnly = true)
    public String getId(String uuid) {
        String id = uuid_id.get(uuid);
        if(StrUtil.isNotEmpty(id) && !Commons.NULL.equals(id)){
            return id;
        }
        String uuidIdKey = RedisKeyEnum.uuid_id.getKey(uuid);
        id = stringRedisTemplate.opsForValue().get(uuidIdKey);
        if(StrUtil.isEmpty(id)){
            synchronized (uuidIdKey){
                id = stringRedisTemplate.opsForValue().get(uuidIdKey);
                if(StrUtil.isEmpty(id)){
                    id = idMappingRepository.findIdByUUID(uuid);
                    if(StrUtil.isEmpty(id)){
                        id = Commons.NULL;
                    }
                }
            }
        }

        if(Commons.NULL.equals(id)){
            uuid_id.put(uuid,id, Duration.ofDays(1).getSeconds()*1000);
            stringRedisTemplate.opsForValue().set(uuidIdKey, id, Duration.ofSeconds(1));
            return null;
        }else{
            uuid_id.put(uuid,id, Duration.ofDays(3).getSeconds()*1000);
            stringRedisTemplate.opsForValue().set(uuidIdKey, id, Duration.ofDays(3));
        }
        return id;
    }

    @Override
    public List<String> getId(List<String> uuids) {
        return uuids.stream().map(this::getId).filter(StrUtil::isNotEmpty).collect(Collectors.toList());
    }

    @Override
    public String idZip(String id) {
        if(!NumberUtil.isNumber(id)){
            return id;
        }
        return com.tinem.platform.module.commons.util.NumberUtil.zipNumber(Long.parseLong(id));
    }

    @Override
    public String idUnZip(String id) {
        return com.tinem.platform.module.commons.util.NumberUtil.unZipNumber(id).toString();
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        ID_MAPPING_SERVICE = event.getApplicationContext().getBean(IdMappingService.class);
    }
}
